// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Data.Common;
using JetBrains.Annotations;
using SnapObjects.Commons;
using System.Threading;
using System.Threading.Tasks;

namespace SnapObjects.Data
{
    /// <summary>
    ///     Represents the database transaction.
    /// </summary>
    public class AdoDbTransaction : IAdoDbTransaction
    {
        private readonly ICollection<Exception> _exceptions;
        private readonly IAdoDbConnection _adoDbConnection;
        private readonly DbTransaction _dbTransaction;
        private readonly bool _transactionOwned;
        private bool _connectionClosed;
        private bool _disposed;

        public AdoDbTransaction(
             [NotNull] IAdoDbConnection connection,
             [NotNull] DbTransaction transaction,
             bool transactionOwned)
        {
            Check.NotNull(connection, nameof(connection));
            Check.NotNull(transaction, nameof(transaction));

            if (connection.DbConnection != transaction.Connection)
            {
                throw new InvalidOperationException("Transaction associated with different connection.");
            }

            _adoDbConnection = connection;

            _dbTransaction = transaction;

            _transactionOwned = transactionOwned;

            _exceptions = new List<Exception>();
        }

        /// <summary>
        ///     Gets the GUID of the current IAdoDbTransaction object.
        /// </summary>
        public virtual Guid TransactionId { get; } = Guid.NewGuid();

        /// <summary>
        ///     Gets the internal DbTransaction object.
        /// </summary>
        public virtual DbTransaction DbTransaction => _dbTransaction;

        /// <summary>
        ///     Commits the database transaction.
        /// </summary>
        public virtual void Commit()
        {
            try
            {
                if (_exceptions.Count > 0)
                {
                    throw new AggregateException(_exceptions);
                }

                _dbTransaction.Commit();

                TrackLogger.LogInformation(Properties.Resource.DATABASE_TRANSACTION_COMMITTED);
            }
            catch
            {
                throw;
            }

            this.ClearTransaction();
        }

        /// <summary>
        ///     Rolls back the database transaction.
        /// </summary>
        public virtual void Rollback()
        {
            try
            {
                _dbTransaction.Rollback();

                TrackLogger.LogInformation(Properties.Resource.DATABASE_TRANSACTION_ROLLEDBACK);
            }
            catch
            {
                throw;
            }

            this.ClearTransaction();
        }

        public virtual Task CommitAsync()
        {
            return this.CommitAsync(default);
        }

        public virtual async Task CommitAsync(CancellationToken cancellationToken)
        {
            try
            {
                if (_exceptions.Count > 0)
                {
                    throw new AggregateException(_exceptions);
                }

                await _dbTransaction.CommitAsync(cancellationToken);

                TrackLogger.LogInformation(Properties.Resource.DATABASE_TRANSACTION_COMMITTED);

                this.ClearTransaction();
            }
            catch
            {
                throw;
            }
        }

        public virtual Task RollbackAsync()
        {
            return this.RollbackAsync(default);
        }

        public virtual async Task RollbackAsync(CancellationToken cancellationToken)
        {
            try
            {
                await _dbTransaction.RollbackAsync(cancellationToken);

                TrackLogger.LogInformation(Properties.Resource.DATABASE_TRANSACTION_ROLLEDBACK);

                this.ClearTransaction();
            }
            catch
            {
                throw;
            }
        }

        public virtual void Dispose()
        {
            if (!_disposed)
            {
                _disposed = true;

                /*if (_transactionOwned)
                {
                    _dbTransaction.Dispose();
                }*/

                this.ClearTransaction();
            }
        }

        internal void LogException(Exception exception)
        {
            _exceptions.Add(exception);
        }

        private void ClearTransaction()
        {
            _exceptions.Clear();

            _adoDbConnection.UseTransaction(null);

            if (!_connectionClosed)
            {
                _connectionClosed = true;

                _adoDbConnection.Close();
            }

            if (_transactionOwned)
            {
                _dbTransaction.Dispose();
            }
        }
    }
}
